/**
  data-receiver.c - Created by Timothy Morey on 3/20/2012.
  
  This file contains an implementation of the data-receiver.h interface.
*/

#ifdef __WINDOWS__

#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#include <WinSock2.h>
#include <WS2tcpip.h>
#pragma warning(disable:4996)

#else

#define INVALID_SOCKET -1
#define SOCKET_ERROR -1
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>

#endif

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "data-receiver.h"
#include "mutex.h"


/*****************************************************************************
 * Local definitions:
 *****************************************************************************/

typedef struct
{
  DataReceiver Base;
  int ReceiveThreadActive;
  Mutex FrameMutex;

#ifdef __WINDOWS__
  HANDLE ReceiveThreadHandle;
  DWORD ReceiveThreadId;
  SOCKET ConnectSocket;
#else
  pthread_t ReceiveThread;
  int ConnectSocket;
#endif
} DataReceiverEx;

int ConnectToServer(DataReceiverEx* drx);
void Disconnect(DataReceiverEx* drx);
int ReceiveFrame(DataReceiverEx* drx);

#ifdef __WINDOWS__
DWORD WINAPI ReceiveThreadEntryPoint(LPVOID param);
#else
void* ReceiveThreadEntryPoint(void* param);
#endif


/*****************************************************************************
 * data-receiver.h implementations:
 *****************************************************************************/

DataReceiver* CreateDataReceiver(const char* server, const char* port)
{
  DataReceiverEx* retval =
    (DataReceiverEx*)malloc(sizeof(DataReceiverEx));

  memset(retval, 0, sizeof(DataReceiverEx));
  strcpy(retval->Base.ServerName, server);
  strcpy(retval->Base.ServerPort, port);
  retval->Base.Status = Idle;
  retval->ReceiveThreadActive = 0;
  retval->ConnectSocket = INVALID_SOCKET;

#ifdef __WINDOWS__
  retval->ReceiveThreadHandle = 0;
  retval->ReceiveThreadId = -1;
#else
  retval->ConnectSocket = -1;
#endif

  return (DataReceiver*)retval;
}

void DestroyDataReceiver(DataReceiver* dr)
{
  if(dr)
  {
    DataReceiverEx* drx = (DataReceiverEx*)dr;
    if(drx->Base.Status > Idle)
      StopDataReceiver(dr);
    DestroyMutex(&drx->FrameMutex);

#ifdef __WINDOWS__
    CloseHandle(drx->ReceiveThreadHandle);
#endif

    if(drx->Base.FrameBuffer)
      free(drx->Base.FrameBuffer);
    free(drx);
  }
}

int LockFrameBuffer(DataReceiver* dr)
{
  int retval = 0;

  if(dr)
    retval = GetLock(((DataReceiverEx*)dr)->FrameMutex);

  return retval;
}

void ReleaseFrameBufferLock(DataReceiver* dr)
{
  if(dr)
    ReleaseLock(((DataReceiverEx*)dr)->FrameMutex);
}

int StartDataReceiver(DataReceiver* dr)
{
  int retval = 0;

  if(dr)
  {
    DataReceiverEx* drx = (DataReceiverEx*)dr;

#ifdef __WINDOWS__

    drx->ReceiveThreadHandle = 
      CreateThread(0, 0, ReceiveThreadEntryPoint, drx, 0, &drx->ReceiveThreadId);

    if(drx->ReceiveThreadHandle)
    {
      drx->ReceiveThreadActive = 1;
      retval = 1;
    }

#else

    if(0 == pthread_create(&drx->ReceiveThread, 0, ReceiveThreadEntryPoint, drx))
    {
      drx->ReceiveThreadActive = 1;
      retval = 1;
    }

#endif
  }

  return retval;
}

void StopDataReceiver(DataReceiver* dr)
{
  if(dr)
  {
    DataReceiverEx* drx = (DataReceiverEx*)dr;
    drx->ReceiveThreadActive = 0;

#ifdef __WINDOWS__

    WaitForSingleObject(drx->ReceiveThreadHandle, INFINITE);

#else

    pthread_join(drx->ReceiveThread, 0);

#endif
  }
}

/*****************************************************************************
 * Private function implementations:
 *****************************************************************************/

int ConnectToServer(DataReceiverEx* drx)
{
  int retval = 1;

  if(drx)
  {
    struct addrinfo hints;
    struct addrinfo* match;
    int res = 0;

    memset(&hints, 0, sizeof(hints));
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_protocol = IPPROTO_TCP;
    res = getaddrinfo(drx->Base.ServerName, 
                      drx->Base.ServerPort, 
                      &hints, 
                      &match);
    if(0 != res)
    {
      fprintf(stderr, "getaddrinfo failed with return code: %d\n", res);
      retval = 0;
    }

    if(retval)
    {
      drx->ConnectSocket = socket(match->ai_family, 
                                  match->ai_socktype, 
                                  match->ai_protocol);

      if(INVALID_SOCKET == drx->ConnectSocket)
      {
        fprintf(stderr, "Failed to create socket\n");
        retval = 0;
      }
    }

    if(retval)
    {
      res = connect(drx->ConnectSocket, match->ai_addr, (int)match->ai_addrlen);

      if(0 != res)
      {
        fprintf(stderr, "Failed to connect socket\n");

#ifdef __WINDOWS__
        closesocket(drx->ConnectSocket);
#else
        close(drx->ConnectSocket);
#endif

        drx->ConnectSocket = INVALID_SOCKET;
        retval = 0;
      }
    }
  }
  else
  {
    retval = 0;
  }

  return retval;
}

void Disconnect(DataReceiverEx* drx)
{
  if(drx && INVALID_SOCKET != drx->ConnectSocket)
  {

#ifdef __WINDOWS__
    shutdown(drx->ConnectSocket, SD_BOTH);
    closesocket(drx->ConnectSocket);
#else
    shutdown(drx->ConnectSocket, SHUT_RDWR);
    close(drx->ConnectSocket);
#endif

  }
}

int ReceiveFrame(DataReceiverEx* drx)
{
  int retval = 0;

  if(drx && INVALID_SOCKET != drx->ConnectSocket)
  {
    char frameSizeBuf[4];
    int frameSize = -1;
    int r = 0;
    int received = 0;

    received = recv(drx->ConnectSocket, frameSizeBuf, 4, 0);
    if(received == 4)
    {
      if(sizeof(int) == 4)
        frameSize = *((int*)frameSizeBuf);
      else
        fprintf(stderr, "Architecture not yet supported: sizeof(int) != 4\n");
    }

    if(frameSize > 0 && frameSize != drx->Base.FrameBufferSize)
    {
      /* TODO: lock frame buffer before resizing */

      if(drx->Base.FrameBuffer)
        free(drx->Base.FrameBuffer);

      drx->Base.FrameBuffer = (char*)malloc(frameSize);
      drx->Base.FrameBufferSize = frameSize;
    }

    if(frameSize > 0)
    {
      received = 0;
      while(received < frameSize)
      {
        r = recv(drx->ConnectSocket, 
                 drx->Base.FrameBuffer + received, 
                 drx->Base.FrameBufferSize - received, 
                 0);
        if(r > 0)
          received += r;
        else if(SOCKET_ERROR == r)
        {
          fprintf(stderr, "Socket error on recv\n");
          break;
        }
      }

      if(received == frameSize)
      {
        drx->Base.FrameId++;
        retval = 1;
      }
    }
  }

  return retval;
}

#ifdef __WINDOWS__
DWORD WINAPI ReceiveThreadEntryPoint(LPVOID param)
#else
void* ReceiveThreadEntryPoint(void* param)
#endif
{
  if(param)
  {
    DataReceiverEx* drx = (DataReceiverEx*)param;
    ConnectToServer(drx);
    drx->Base.Status = Receiving;
    while(drx->ReceiveThreadActive)
      ReceiveFrame(drx);
    drx->Base.Status = Idle;
    Disconnect(drx);
  }

  return 0;
}
